/** * @file BleManager.java * @brief BleManager handles all kind of interface with ble devices * @author Ryan Lee (strike77@gmail.com) * @date May/22/2015 */ package com.firstbuild.commonframework.blemanager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.os.AsyncTask; import android.os.Handler; import android.util.Log; import com.firstbuild.androidapp.productmanager.ProductManager; import com.firstbuild.tools.MainQueue; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; public class BleManager { public final static String CLIENT_CHARACTERISTIC_CONFIGURATION_UUID = "00002902-0000-1000-8000-00805f9b34fb"; public static BleManager instance = new BleManager(); private final String TAG = getClass().getSimpleName(); // Flag for checking device scanning state public boolean isScanning = false; private HashMap<String, BluetoothDevice> scannedDevices = new HashMap<String, BluetoothDevice>(); // Bluetooth adapter handler private BluetoothAdapter bluetoothAdapter = null; private BluetoothManager bluetoothManager = null; // Post Delayed handler private Handler handler = new Handler(); private Runnable stopScanRunnable = null; private Context context = null; private HashMap<String, BleListener> callbacks = null; // private HashMap<String, BluetoothGatt> connectedGatts = new HashMap<>(); // contains connected Gatt server private ConcurrentHashMap<String, BluetoothGatt> bluetoothGattMap = new ConcurrentHashMap<>(); // contains connected Gatt server private ConcurrentHashMap<String, Runnable> periodicConnectionCheckerMap = new ConcurrentHashMap<>(); // contains periodic connection checker runnable // Use BlockingQueue for thread-safety LinkedBlockingQueue<BleOperation> operationQ = new LinkedBlockingQueue<>(); private AsyncTask<Void, Void, Void> currentOperationTimeout; private BleOperation currentOperation = null; /** * Device scan callback */ private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice bluetoothDevice, int rssi, byte[] scanRecord) { try { if (bluetoothDevice != null && bluetoothDevice.getName() != null) { scannedDevices.put(bluetoothDevice.getAddress(), bluetoothDevice); // Notify updates to other modules sendUpdate("onScanDevices", new Object[]{scannedDevices}); } } catch (Exception e) { e.printStackTrace(); } } }; private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); Log.d(TAG, "onConnectionStateChange IN"); final String address = gatt.getDevice().getAddress(); if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { Log.i(TAG, "Connected to GATT server. " + address + " name : " + gatt.getDevice().getName()); Log.i(TAG, "Attempting to start service discovery:" + gatt.discoverServices()); // Save connected gatt server bluetoothGattMap.put(address, gatt); removePeriodicConnectionScheduled(address); } else if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_DISCONNECTED) { Log.i(TAG, "Disconnected from GATT server." + address + " name : " + gatt.getDevice().getName()); // if bluetooth is turned off, remove gatt and close on it. if(bluetoothAdapter.isEnabled() == false) { Log.d(TAG, "Bluetooth was turned off so call gatt.close as well as removing gatt instance"); close(gatt, address); } // As we don't get callback for connected, periodically trying to connect to the device after disconnection schedulePeriodicConnection(address); // make sure to run the operation for other Gatt client that might be connected doOperation(); } else if(status != BluetoothGatt.GATT_SUCCESS) { Log.d(TAG, "[HANS] onConnectionStateChange status : " + status + " Device name : " + gatt.getDevice().getName()); if (status == 133) { // device doesn't adverise or reachable Log.d(TAG, "onConnectionStateChange : device doesn't advertise or not reachable!!!"); close(gatt, address); } else if(status == 8) { // lost the connection link(LINK_LOSS) due to no response from remote device or Timeout happened. Log.d(TAG, "onConnectionStateChange : lost the connection link(LINK_LOSS) due to no response from remote device or Timeout!!!"); schedulePeriodicConnection(address); } sendUpdate("onConnectionStateChanged", new Object[]{address, BluetoothProfile.STATE_DISCONNECTED}); // make sure to run the operation for other Gatt client that might be connected doOperation(); return; } sendUpdate("onConnectionStateChanged", new Object[]{address, newState}); } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); String address = gatt.getDevice().getAddress(); Log.d(TAG, "onServicesDiscovered " + address + " name : " + gatt.getDevice().getName()); if (status == BluetoothGatt.GATT_SUCCESS) { displayGattServices(gatt); List<BluetoothGattService> bleGattServices = gatt.getServices(); sendUpdate("onServicesDiscovered", new Object[]{address, bleGattServices}); MainQueue.postDelayed(new Runnable() { @Override public void run() { executeNextOperation(); } }, 1000); } else { Log.d(TAG, "onServicesDiscovered NOT GATT_SUCCESS: " + status); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); Log.d(TAG, "onCharacteristicRead IN"); // Retrieves address String address = gatt.getDevice().getAddress(); // Retrieves uuid and value String uuid = characteristic.getUuid().toString(); Log.d(TAG, "Read Characteristic UUID: " + uuid); byte[] value = characteristic.getValue(); if (value != null) { printGattValue(value); } sendUpdate("onCharacteristicRead", new Object[]{address, uuid, value, status}); executeNextOperation(); } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); Log.d(TAG, "onCharacteristicWrite"); // Retrieves address String address = gatt.getDevice().getAddress(); // Retrieves uuid and value String uuid = characteristic.getUuid().toString(); Log.d(TAG, "Write Characteristic UUID: " + uuid + " status : " + status); byte[] value = characteristic.getValue(); printGattValue(value); sendUpdate("onCharacteristicWrite", new Object[]{address, uuid, value, status}); executeNextOperation(); } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); Log.d(TAG, "onCharacteristicChanged"); // Retrieves address String address = gatt.getDevice().getAddress(); // Retrieves uuid and value String uuid = characteristic.getUuid().toString(); Log.d(TAG, "Characteristic UUID: " + uuid); byte[] value = characteristic.getValue(); printGattValue(value); sendUpdate("onCharacteristicChanged", new Object[]{address, uuid, value}); } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorWrite(gatt, descriptor, status); Log.d(TAG, "onDescriptorWrite"); // Retrieves address String address = gatt.getDevice().getAddress(); // Retrieves uuid and value String uuid = descriptor.getUuid().toString(); Log.d(TAG, "Characteristic UUID: " + uuid); byte[] value = descriptor.getValue(); sendUpdate("onDescriptorWrite", new Object[]{address, uuid, value, status}); executeNextOperation(); } }; private void close(BluetoothGatt gatt, String address) { if(bluetoothGattMap.containsKey(address)) { bluetoothGattMap.remove(address); } gatt.close(); } private void removePeriodicConnectionScheduled(String address) { if(periodicConnectionCheckerMap.containsKey(address)) { // remove check connection runnable from MainQueue and HashMap Log.d(TAG, "Remove periodic connection checker runnable "); MainQueue.removeCallbacks(periodicConnectionCheckerMap.remove(address)); } } private void schedulePeriodicConnection(String address) { if(periodicConnectionCheckerMap.containsKey(address) == false) { periodicConnectionCheckerMap.put(address, new PeriodicConnectionChecker(address)); } Runnable r = periodicConnectionCheckerMap.get(address); MainQueue.removeCallbacks(r); MainQueue.postDelayed(r, 10000); } public BleManager() { // Default constructor } public static BleManager getInstance() { return instance; } /** * Initialize BleManager * * @param context activity's context */ public void initBleManager(Context context) { Log.d(TAG, "initBleManager IN"); if (this.context == null) { this.context = context; bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); // Get bluetooth adaptor bluetoothAdapter = bluetoothManager.getAdapter(); } } /** * Add a listener to subscribe ble event * * @param listener listener instance */ public void addListener(BleListener listener) { if (callbacks == null) { callbacks = new HashMap<String, BleListener>(); } else { // Do nothing } // Key is listener is casted string // Value is listener itself. callbacks.put(listener.toString(), listener); } /** * Remove a listener for ble event * * @param listener listener instance */ public void removeListener(BleListener listener) { if (callbacks != null && !callbacks.isEmpty()) { callbacks.remove(listener.toString()); Log.d(TAG, "Remove Listener: " + listener); } else { // Do nothing } } /** * Connect to a ble device * * @return connection success or failed */ public BluetoothDevice connect(final String address) { Log.d(TAG, "connect IN"); if (bluetoothAdapter == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return null; } BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); Log.d(TAG, "Trying to create a new connection."); return device; } /** * Check bluetooth feature in the phone is enabled * * @return enabled or disabled */ public boolean isBluetoothEnabled() { Log.d(TAG, "isBluetoothEnabled IN"); if(bluetoothAdapter == null){ return false; } return bluetoothAdapter.isEnabled(); } /** * start device scan for duration * * @param duration 1 to 120 sec. */ public boolean startScan(final int duration) { Log.d(TAG, "startScan IN"); boolean result = false; if (duration > 0 && duration <= 120) { // Check duration isScanning = true; // TODO: hans 16. 5. 31. consider replacing this with startScan(List, ScanSettings, ScanCallback) bluetoothAdapter.startLeScan(leScanCallback); // Stops scanning after a pre-defined scan period. handler.postDelayed(setStopScanRunnable(), duration); result = true; sendUpdate("onScanStateChanged", new Object[]{BleValues.START_SCAN}); } else { Log.d(TAG, "duration is out of range(1 - 120 sec): " + duration); result = false; } return result; } private Runnable setStopScanRunnable() { // Create runnable object for stop scanning stopScanRunnable = new Runnable() { @Override public void run() { Log.d(TAG, "Scan ble device time out!"); stopScan(); } }; return stopScanRunnable; } /** * Send update to subscribers * * @param listener subscriber * @param args corresponding arguments */ public void sendUpdate(String listener, Object... args) { Log.d(TAG, "sendUpdate IN"); if (listener.equals("onCharacteristicChanged") || listener.equals("onCharacteristicRead") ) { ProductManager.getInstance().updateErd((String) args[0], (String) args[1], (byte[]) args[2]); } else { // Do nothing } // Clone hashmap to avoid java.util.ConcurrentModificationException HashMap<String, BleListener> callbackClone = (HashMap) callbacks.clone(); for (Map.Entry<String, BleListener> entry : callbackClone.entrySet()) { BleListener callback = entry.getValue(); Log.d(TAG, "sendUpdate LOOP"); if (callback != null && listener.equals("onScanStateChanged")) { callback.onScanStateChanged((int) args[0]); } else if (callback != null && listener.equals("onScanDevices")) { callback.onScanDevices((HashMap<String, BluetoothDevice>) args[0]); } else if (callback != null && listener.equals("onConnectionStateChanged")) { callback.onConnectionStateChanged((String) args[0], (int) args[1]); } else if (callback != null && listener.equals("onServicesDiscovered")) { callback.onServicesDiscovered((String) args[0], (List<BluetoothGattService>) args[1]); } else if (callback != null && listener.equals("onCharacteristicChanged")) { callback.onCharacteristicChanged((String) args[0], (String) args[1], (byte[]) args[2]); } else if (callback != null && listener.equals("onCharacteristicRead")) { callback.onCharacteristicRead((String) args[0], (String) args[1], (byte[]) args[2], (int) args[3]); } else if (callback != null && listener.equals("onCharacteristicWrite")) { callback.onCharacteristicWrite((String) args[0], (String) args[1], (byte[]) args[2], (int) args[3]); } else if (callback != null && listener.equals("onDescriptorWrite")) { callback.onDescriptorWrite((String) args[0], (String) args[1], (byte[]) args[2], (int) args[3]); } else { // Do nothing } } Log.d(TAG, "sendUpdate OUT"); } /** * Stop scanning BLE devices */ public void stopScan() { Log.d(TAG, "stopScan IN"); isScanning = false; bluetoothAdapter.stopLeScan(leScanCallback); // Stop postDelayed method if (stopScanRunnable != null) { Log.d(TAG, "Remove delayed stopScan task"); handler.removeCallbacks(stopScanRunnable); } sendUpdate("onScanStateChanged", new Object[]{BleValues.STOP_SCAN}); } /** * Start device scan for 10 sec */ public void startScan() { Log.d(TAG, "startScan IN"); isScanning = true; // Set callback to leScanCallback bluetoothAdapter.startLeScan(leScanCallback); // Stops scanning after a pre-defined scan period. handler.postDelayed(setStopScanRunnable(), BleValues.SCAN_PERIOD); sendUpdate("onScanStateChanged", new Object[]{BleValues.START_SCAN}); } /** * Print GATT services and characteristics */ public void displayGattServices(BluetoothGatt gatt) { Log.d(TAG, "displayGattServices IN"); List<BluetoothGattService> bleGattServices = gatt.getServices(); if (bleGattServices != null) { // Loops through available GATT Services. for (BluetoothGattService gattService : bleGattServices) { String serviceUuid = gattService.getUuid().toString(); Log.d(TAG, "Service UUID: " + serviceUuid); List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics(); // Loops through available Characteristics. for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { String CharacteristicUuid = gattCharacteristic.getUuid().toString(); Log.d(TAG, "Characteristic UUID: " + CharacteristicUuid + ", Permission: " + gattCharacteristic.getPermissions() + ", Write Type: " + gattCharacteristic.getWriteType()); if (gattCharacteristic.getValue() != null) { printGattValue(gattCharacteristic.getValue()); } } Log.d(TAG, "====================================="); } } } /** * Add ReadChracteristics operation to the queue. * * @param device BluetoothDevice object. * @param characteristicsUuid UUID to read. */ public void readCharacteristics(BluetoothDevice device, String characteristicsUuid) { Log.d(TAG, "operationQ.offer readCharacteristics"); BleOperationReadCharacteristics read = new BleOperationReadCharacteristics(device, characteristicsUuid); if(isBleOperationExisting(read) == false) { operationQ.offer(read); }else { // Don't add as there is already same operation in the Q Log.d(TAG, "readCharacteristics : current operation is already queued !"); } doOperation(); } /** * Add WriteChracteristics operation to the queue. * * @param device BluetoothDevice object. * @param characteristicsUuid UUID to write. * @param values actual value to write. */ public void writeCharacteristics(BluetoothDevice device, String characteristicsUuid, byte[] values) { Log.d(TAG, "operationQ.offer writeCharacteristics"); BleOperationWriteCharateristics write = new BleOperationWriteCharateristics(device, characteristicsUuid, values); if(isBleOperationExisting(write) == false) { operationQ.offer(write); }else { // Don't add as there is already same operation in the Q Log.d(TAG, "writeCharacteristics : current operation is already queued !"); } doOperation(); } /** * Add Notification operation to the queue. * * @param device BluetoothDevice object. * @param characteristicsUuid UUID for notification. * @param isEnabled enable/disable of notification. */ public void setCharacteristicNotification(BluetoothDevice device, String characteristicsUuid, boolean isEnabled) { Log.d(TAG, "operationQ.offer setCharacteristicNotification"); BleOperationSetNotification notification = new BleOperationSetNotification(device, characteristicsUuid, isEnabled); if(isBleOperationExisting(notification) == false) { operationQ.offer(notification); }else { // Don't add as there is already same operation in the Q Log.d(TAG, "setCharacteristicNotification : current operation is already queued !"); } doOperation(); } /** * Add remove device operation to the queue. * * @param device BluetoothDevice object. */ public void removeDevice(BluetoothDevice device) { Log.d(TAG, "operationQ.offer disconnect"); // remove current operations that is scheduled to be executed on removed device cancelOperationFromDeletedDevice(device); // remove any scheduled periodic connection runnable removePeriodicConnectionScheduled(device.getAddress()); // if it is connected, then let it call gatt.close() if(isConnectedDevice(device)) { if(bluetoothGattMap.containsKey(device.getAddress())) { bluetoothGattMap.get(device.getAddress()).close(); bluetoothGattMap.remove(device.getAddress()); } } doOperation(); } /** * Remove all operations which are schedule to be run on deleted device from the Queue * * @param device Device to be deleted */ private void cancelOperationFromDeletedDevice(BluetoothDevice device) { for (Iterator<BleOperation> iterator = operationQ.iterator(); iterator.hasNext(); ) { BleOperation operation = iterator.next(); if (operation.getDevice().getAddress() == device.getAddress()) { Log.d(TAG, "cancelOperationFromRemovedDevice remove operation :" + operation.toString()); iterator.remove(); } } // Cancel current Operation if the operation is for removed device if(currentOperation != null && currentOperation.getDevice().getAddress() == device.getAddress()) { setCurrentOperation(null); } } /** * Remove all operations pertaining to device passed in, which are schedule to be run on from the Queue * * @param device Device where scheduled operations should be deleted */ public void cancelOperations(BluetoothDevice device) { // reuse function cancelOperationFromDeletedDevice(device); // execute the next queued operation doOperation(); } /** * Cancel current operation due to expired timer. */ public synchronized void cancelCurrentOperation() { Log.d(TAG, "cancelCurrentOperation ****************"); // if(operationQ.size() == 0) { // executeNextOperation(); // return; // } if(currentOperation != null) { LinkedList<BleOperation> tempOperations = new LinkedList<>(); for (Iterator<BleOperation> iterator = operationQ.iterator(); iterator.hasNext();) { BleOperation operation = iterator.next(); if(operation.getDevice() == currentOperation.getDevice()) { Log.d(TAG, "cancelCurrentOperation removed operationQ has same device :"+currentOperation.getDevice().getAddress()); tempOperations.add(operation); iterator.remove(); } } for( BleOperation tempOperation : tempOperations){ operationQ.offer(tempOperation); } if(isBleOperationExisting(currentOperation) == false) { operationQ.offer(currentOperation); } else { // Do nothing as there is an already same operation to be executed } } executeNextOperation(); } /** * Pick the next operation from the queue and execute. */ private synchronized void doOperation() { if (currentOperation != null) { Log.d(TAG, "tried to doOperation, but currentOperation was not null, " + currentOperation); return; } if (operationQ.size() == 0) { Log.d(TAG, "Queue empty, doOperation loop stopped."); return; } final BleOperation operation = operationQ.poll(); final BluetoothDevice device = operation.getDevice(); Log.d(TAG, "Driving Gatt queue, size will now : " + operationQ.size()); setCurrentOperation(operation); if (currentOperationTimeout != null) { Log.d(TAG, "Good to cancel timer and go to next operation since we got call back before the time out"); currentOperationTimeout.cancel(true); } if (!bluetoothGattMap.containsKey(device.getAddress())) { operation.setTimeoutTime(BleOperation.CONNECT_TIMEOUT_IN_MILLIS); } currentOperationTimeout = new AsyncTask<Void, Void, Void>() { @Override protected synchronized Void doInBackground(Void... voids) { try { Log.d(TAG, "Starting to do a background timeout"); wait(operation.getTimoutTime()); } catch (InterruptedException e) { Log.d(TAG, "was interrupted out of the timeout"); } if (isCancelled()) { Log.d(TAG, "The timeout was cancelled, so we do nothing."); return null; } Log.d(TAG, "Timeout ran to completion, time to Abort!!!!!!!!!!!!!"); cancelCurrentOperation(); return null; } @Override protected synchronized void onCancelled() { super.onCancelled(); notify(); } }.execute(); // If connected and service is discovered, then let it execute the next operations if(isConnectedDevice(device) && bluetoothGattMap.containsKey(device.getAddress()) && bluetoothGattMap.get(device.getAddress()).getServices().size() > 0) { Log.d(TAG, "found address in bluetoothGattMap"); executeOperation(bluetoothGattMap.get(device.getAddress()), operation); } else { Log.d(TAG, "connecting to the GATT server"); connectToGattServer(device.getAddress()); } } /** * Connects to the GATT server hosted on the Bluetooth LE device. * * @param address The device address of the destination device. * * @return Return true if the connection is initiated successfully. The connection result * is reported asynchronously through the * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} * callback. */ private boolean connectToGattServer(String address) { if(bluetoothAdapter == null || address == null) { Log.d(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } if(bluetoothAdapter.isEnabled() == false) { Log.d(TAG, "Bluetooth is disabled ! Skip reconnection trial to GATT server : " + address); return false; } // previously connected device, reconnect if(bluetoothGattMap.containsKey(address)) { BluetoothGatt gatt = bluetoothGattMap.get(address); Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection. The Gatt Server name : " + gatt.getDevice().getName()); if (gatt.connect()) { return true; } else { Log.d(TAG, "reconnection fails ! The Gatt Server name : " + gatt.getDevice().getName()); return false; } } final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); if (device == null) { Log.d(TAG, "Device not found. Unable to connect."); return false; } device.connectGatt(context, false, gattCallback); return true; } /** * Execute given operation. * * @param bluetoothGatt BluetoothDevice object. * @param operation BleOperation object. */ private void executeOperation(BluetoothGatt bluetoothGatt, BleOperation operation) { Log.d(TAG, "executeOperation IN"); if (operation == null || operation != currentOperation) { Log.d(TAG, "current operation is null or operation doesn't equals to currentOperation"); return; } Log.d(TAG, "execute operation"); operation.execute(bluetoothGatt); if (!operation.hasCallback()) { executeNextOperation(); } } /** * Put given operation into currentOperation * * @param operation BleOperation object. */ private synchronized void setCurrentOperation(BleOperation operation) { this.currentOperation = operation; } private boolean isBleOperationExisting(BleOperation op) { return operationQ.contains(op); } public void printGattValue(byte[] values) { StringBuilder hexValue = new StringBuilder(); // Print value in hexa decimal format System.out.print("Value: "); for (byte value : values) { hexValue.append(String.format("0x%02x ", value)); } System.out.println(hexValue); } public boolean setDeviceName(String name) { boolean result = false; bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter != null) { bluetoothAdapter.setName(name); result = true; } return result; } public String getDeviceName() { String result = null; bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter != null) { result = bluetoothAdapter.getName(); } Log.d(TAG, "device name: " + result); return result; } public void pairDevice(BluetoothDevice device) { Log.d(TAG, "pairDevice IN"); try { Method method = device.getClass().getMethod("createBond", (Class[]) null); method.invoke(device, (Object[]) null); } catch (Exception e) { e.printStackTrace(); } Log.d(TAG, "pairDevice OUT"); } public boolean unpair(final String address) { boolean result = false; if (address != null) { // Retrieves paired device list Set<BluetoothDevice> pairedDevice = bluetoothAdapter.getBondedDevices(); if (pairedDevice != null && pairedDevice.size() > 0) { // Iterate all the device in the list for (BluetoothDevice bluetoothDevice : pairedDevice) { Log.d(TAG, bluetoothDevice.getName() + "\n" + bluetoothDevice.getAddress()); if (bluetoothDevice.getAddress().equals(address)) { // Device found unpairDevice(bluetoothDevice); result = true; } } } } return result; } public void unpairDevice(BluetoothDevice device) { try { Method method = device.getClass().getMethod("removeBond", (Class[]) null); method.invoke(device, (Object[]) null); } catch (Exception e) { e.printStackTrace(); } } private boolean isConnectedDevice(BluetoothDevice device) { boolean ret = false; if(bluetoothManager == null || device == null) { Log.d(TAG, "bluetoothManager not initialized or device is null"); return false; } int state = bluetoothManager.getConnectionState(device, BluetoothProfile.GATT_SERVER); if(state == BluetoothProfile.STATE_CONNECTED) { Log.d(TAG, "Device : " + device.getName() + " is connected ! "); ret = true; }else { Log.d(TAG, "Device : " + device.getName() + " is not connected ! Connection state : " + state); } return ret; } private void executeNextOperation() { setCurrentOperation(null); doOperation(); } /** * Close all GATT connection * This is called when app is closed and need to clear all connections * */ public synchronized void closeAllActiveGattClients() { Log.d(TAG, "closeAllActiveGattClients IN"); for(Map.Entry<String, BluetoothGatt> elem : bluetoothGattMap.entrySet()) { BluetoothGatt item = elem.getValue(); Log.d(TAG, "closeAllActiveGattClients : close the connection from : " + item.getDevice().getName()); item.close(); } bluetoothGattMap.clear(); periodicConnectionCheckerMap.clear(); } /** * This tries to connect to the GATT server every 10 seconds */ private class PeriodicConnectionChecker implements Runnable { private final String TAG = PeriodicConnectionChecker.class.getSimpleName(); String targetDeviceAddress; public PeriodicConnectionChecker(String address) { targetDeviceAddress = address; } @Override public void run() { Log.d(TAG, "PeriodicConnectionChecker run() : trying to connect to the GattServer"); connectToGattServer(targetDeviceAddress); MainQueue.removeCallbacks(this); MainQueue.postDelayed(this, 10000); } } }